﻿/********************************************************************
	created:	2011-8-17   11:56
	author:		Lifan Su
	
	purpose:	Implement of SHA-512 functions.
*********************************************************************/

#include "sha512.h"
#include <cvt_int_mem.h>
#include "hex_to_char.h"

#include <assert.h>
#include <_stdint.h>
#include <string.h>

static const int blocksize = 0x80;
static const int blockmask = 0x7F;
static const int endmaxsize = 0x6F;
static const uint8_t paddingch1 = 0x80;
static const uint8_t paddingcha = 0x00;

static const uint64_t sha512_const[] = {
    UINT64_C(0x428a2f98d728ae22), UINT64_C(0x7137449123ef65cd),
    UINT64_C(0xb5c0fbcfec4d3b2f), UINT64_C(0xe9b5dba58189dbbc),
    UINT64_C(0x3956c25bf348b538), UINT64_C(0x59f111f1b605d019),
    UINT64_C(0x923f82a4af194f9b), UINT64_C(0xab1c5ed5da6d8118),
    UINT64_C(0xd807aa98a3030242), UINT64_C(0x12835b0145706fbe),
    UINT64_C(0x243185be4ee4b28c), UINT64_C(0x550c7dc3d5ffb4e2),
    UINT64_C(0x72be5d74f27b896f), UINT64_C(0x80deb1fe3b1696b1),
    UINT64_C(0x9bdc06a725c71235), UINT64_C(0xc19bf174cf692694),

    UINT64_C(0xe49b69c19ef14ad2), UINT64_C(0xefbe4786384f25e3),
    UINT64_C(0x0fc19dc68b8cd5b5), UINT64_C(0x240ca1cc77ac9c65),
    UINT64_C(0x2de92c6f592b0275), UINT64_C(0x4a7484aa6ea6e483),
    UINT64_C(0x5cb0a9dcbd41fbd4), UINT64_C(0x76f988da831153b5),
    UINT64_C(0x983e5152ee66dfab), UINT64_C(0xa831c66d2db43210),
    UINT64_C(0xb00327c898fb213f), UINT64_C(0xbf597fc7beef0ee4),
    UINT64_C(0xc6e00bf33da88fc2), UINT64_C(0xd5a79147930aa725),
    UINT64_C(0x06ca6351e003826f), UINT64_C(0x142929670a0e6e70),

    UINT64_C(0x27b70a8546d22ffc), UINT64_C(0x2e1b21385c26c926),
    UINT64_C(0x4d2c6dfc5ac42aed), UINT64_C(0x53380d139d95b3df),
    UINT64_C(0x650a73548baf63de), UINT64_C(0x766a0abb3c77b2a8),
    UINT64_C(0x81c2c92e47edaee6), UINT64_C(0x92722c851482353b),
    UINT64_C(0xa2bfe8a14cf10364), UINT64_C(0xa81a664bbc423001),
    UINT64_C(0xc24b8b70d0f89791), UINT64_C(0xc76c51a30654be30),
    UINT64_C(0xd192e819d6ef5218), UINT64_C(0xd69906245565a910),
    UINT64_C(0xf40e35855771202a), UINT64_C(0x106aa07032bbd1b8),

    UINT64_C(0x19a4c116b8d2d0c8), UINT64_C(0x1e376c085141ab53),
    UINT64_C(0x2748774cdf8eeb99), UINT64_C(0x34b0bcb5e19b48a8),
    UINT64_C(0x391c0cb3c5c95a63), UINT64_C(0x4ed8aa4ae3418acb),
    UINT64_C(0x5b9cca4f7763e373), UINT64_C(0x682e6ff3d6b2b8a3),
    UINT64_C(0x748f82ee5defb2fc), UINT64_C(0x78a5636f43172f60),
    UINT64_C(0x84c87814a1f0ab72), UINT64_C(0x8cc702081a6439ec),
    UINT64_C(0x90befffa23631e28), UINT64_C(0xa4506cebde82bde9),
    UINT64_C(0xbef9a3f7b2c67915), UINT64_C(0xc67178f2e372532b),

    UINT64_C(0xca273eceea26619c), UINT64_C(0xd186b8c721c0c207),
    UINT64_C(0xeada7dd6cde0eb1e), UINT64_C(0xf57d4f7fee6ed178),
    UINT64_C(0x06f067aa72176fba), UINT64_C(0x0a637dc5a2c898a6),
    UINT64_C(0x113f9804bef90dae), UINT64_C(0x1b710b35131c471b),
    UINT64_C(0x28db77f523047d84), UINT64_C(0x32caab7b40c72493),
    UINT64_C(0x3c9ebe0a15c9bebc), UINT64_C(0x431d67c49c100d4c),
    UINT64_C(0x4cc5d4becb3e42b6), UINT64_C(0x597f299cfc657e2a),
    UINT64_C(0x5fcb6fab3ad6faec), UINT64_C(0x6c44198c4a475817)
};

static uint64_t rotl(uint64_t val, uint32_t n);
static void block_calc(struct SHA512_c *val, const void *buf);


CRYPT_HASH_PREFIX void CRYPT_HASH_API sha512_d_read(struct SHA512_d *dst, const void *src)
{
    const uint8_t *_src = src;

    assert(dst && src && "Non-null pointer");

    dst->hash[0] = cvt_byte_to_u64_be(_src +  0);
    dst->hash[1] = cvt_byte_to_u64_be(_src +  8);
    dst->hash[2] = cvt_byte_to_u64_be(_src + 16);
    dst->hash[3] = cvt_byte_to_u64_be(_src + 24);
    dst->hash[4] = cvt_byte_to_u64_be(_src + 32);
    dst->hash[5] = cvt_byte_to_u64_be(_src + 40);
    dst->hash[6] = cvt_byte_to_u64_be(_src + 48);
    dst->hash[7] = cvt_byte_to_u64_be(_src + 56);
}

CRYPT_HASH_PREFIX void CRYPT_HASH_API sha512_d_write(void *dst, const struct SHA512_d *src)
{
    uint8_t *_dst = dst;

    assert(dst && src && "Non-null pointer");

    cvt_u64_be_to_byte(_dst +  0, src->hash[0]);
    cvt_u64_be_to_byte(_dst +  8, src->hash[1]);
    cvt_u64_be_to_byte(_dst + 16, src->hash[2]);
    cvt_u64_be_to_byte(_dst + 24, src->hash[3]);
    cvt_u64_be_to_byte(_dst + 32, src->hash[4]);
    cvt_u64_be_to_byte(_dst + 40, src->hash[5]);
    cvt_u64_be_to_byte(_dst + 48, src->hash[6]);
    cvt_u64_be_to_byte(_dst + 56, src->hash[7]);
}

CRYPT_HASH_PREFIX void CRYPT_HASH_API sha512_d_print(char *dst, const struct SHA512_d *src)
{
    int i, j;

    assert(dst && src && "Non-null pointer");

    for (i = 0; i != 8; ++i) {
        for (j = 16; j != 0; --j) {
            *(dst++) = hex_to_char(((src->hash[i]) >> (4 * j - 4)) & 0xF);
        }
    }

    *dst = '\0';
}

CRYPT_HASH_PREFIX void CRYPT_HASH_API sha512_d_printw(wchar_t *dst, const struct SHA512_d *src)
{
    int i, j;

    assert(dst && src && "Non-null pointer");

    for (i = 0; i != 8; ++i) {
        for (j = 16; j != 0; --j) {
            *(dst++) = hex_to_wchar(((src->hash[i]) >> (4 * j - 4)) & 0xF);
        }
    }

    *dst = L'\0';
}

CRYPT_HASH_PREFIX uint8_t CRYPT_HASH_API sha512_d_less(const struct SHA512_d *lhs, const struct SHA512_d *rhs)
{
    assert(lhs && rhs && "Non-null pointer");

    return (lhs->hash[0] < rhs->hash[0]) || ((lhs->hash[0] == rhs->hash[0]) 
        && ((lhs->hash[1] < lhs->hash[1]) || ((lhs->hash[1] == rhs->hash[1])
        && ((lhs->hash[2] < rhs->hash[2]) || ((lhs->hash[2] == rhs->hash[2])
        && ((lhs->hash[3] < rhs->hash[3]) || ((lhs->hash[3] == rhs->hash[3])
        && ((lhs->hash[4] < rhs->hash[4]) || ((lhs->hash[4] == rhs->hash[4])
        && ((lhs->hash[5] < rhs->hash[5]) || ((lhs->hash[5] == rhs->hash[5])
        && ((lhs->hash[6] < rhs->hash[6]) || ((lhs->hash[6] == rhs->hash[6])
        && ((lhs->hash[7] < rhs->hash[7])))))))))))))));
}

CRYPT_HASH_PREFIX uint8_t CRYPT_HASH_API sha512_d_eq(const struct SHA512_d *lhs, const struct SHA512_d *rhs)
{
    assert(lhs && rhs && "Non-null pointer");

    return (lhs->hash[0] == rhs->hash[0]) && (lhs->hash[1] == rhs->hash[1])
        && (lhs->hash[2] == rhs->hash[2]) && (lhs->hash[3] == rhs->hash[3])
        && (lhs->hash[4] == rhs->hash[4]) && (lhs->hash[5] == rhs->hash[5])
        && (lhs->hash[6] == rhs->hash[6]) && (lhs->hash[7] == rhs->hash[7]);
}

CRYPT_HASH_PREFIX void CRYPT_HASH_API sha512_cvt(struct SHA512_d *dst, const struct SHA512_c *src)
{
    int i;

    assert(dst && src && "Non-null pointer");

    for (i = 0; i != 8; ++i) {
        dst->hash[i] = src->hash[i];
    }
}

CRYPT_HASH_PREFIX void CRYPT_HASH_API sha512_c_init(struct SHA512_c *val)
{
    assert(val && "Non-null pointer");

    val->hash[0] = UINT64_C(0x6a09e667f3bcc908);
    val->hash[1] = UINT64_C(0xbb67ae8584caa73b);
    val->hash[2] = UINT64_C(0x3c6ef372fe94f82b);
    val->hash[3] = UINT64_C(0xa54ff53a5f1d36f1);
    val->hash[4] = UINT64_C(0x510e527fade682d1);
    val->hash[5] = UINT64_C(0x9b05688c2b3e6c1f);
    val->hash[6] = UINT64_C(0x1f83d9abfb41bd6b);
    val->hash[7] = UINT64_C(0x5be0cd19137e2179);

    val->length = 0;
    val->append = 1;
}

CRYPT_HASH_PREFIX void CRYPT_HASH_API sha512_c_append(struct SHA512_c *val, const void *src, uint64_t len)
{
    int buffer_used;
    const uint8_t *_src = src;

    assert(val && src && val->append && "Non-null pointer and append able");

    buffer_used = val->length & blockmask;
    val->length += len;
    while (len >= blocksize - buffer_used) {
        if (buffer_used) {
            memcpy(val->buffer + buffer_used, _src, blocksize - buffer_used);
            block_calc(val, val->buffer);
            _src += (blocksize - buffer_used);
            len -= (blocksize - buffer_used);
            buffer_used = 0;
        } else {
            block_calc(val, _src);
            _src += blocksize;
            len -= blocksize;
        }
    }
    
    memcpy(val->buffer + buffer_used, _src, (size_t)len);
}

CRYPT_HASH_PREFIX void CRYPT_HASH_API sha512_c_close(struct SHA512_c *val)
{
    int i = 0;

    assert(val && val->append && "Non-null pointer and append able");

    if ((val->length & blockmask) <= endmaxsize) {
        val->buffer[val->length & blockmask] = paddingch1;
        memset(val->buffer + (val->length & blockmask) + 1, paddingcha,
            endmaxsize - (val->length & blockmask));
        memset(val->buffer + blocksize - 16, paddingcha, 8);
        cvt_u64_be_to_byte(val->buffer + blocksize - 8, val->length << 3);
        block_calc(val, val->buffer);
    } else {
        val->buffer[val->length & blockmask] = paddingch1;
        memset(val->buffer + (val->length & blockmask) + 1, paddingcha,
            blocksize - 1 - (val->length & blockmask));
        block_calc(val, val->buffer);
        memset(val->buffer,  paddingcha, endmaxsize + 1);
        memset(val->buffer + blocksize - 16, paddingcha, 8);
        cvt_u64_be_to_byte(val->buffer + blocksize - 8, val->length << 3);
        block_calc(val, val->buffer);
    }

    val->append = 0;
}


static uint64_t rotr(uint64_t val, uint32_t n)
{
    return (val >> n) | (val << (64-n));
}

static void block_calc(struct SHA512_c *val, const void *buf)
{
    int i;
    uint64_t t1, t2;
    const uint8_t *_buf;

    uint64_t words_arr[80];
    uint64_t buf_arr[8];

    _buf = buf;

    for (i = 0; i != 8; ++i) {
        buf_arr[i] = val->hash[i];
    }
    for (i = 0; i != 16; ++i) {
        words_arr[i] = cvt_byte_to_u64_be(_buf + 8 * i);
    }
    for (i = 16; i != 80; ++i) {
        words_arr[i] = words_arr[i - 16] + words_arr[i - 7]
        + (rotr(words_arr[i - 15], 1)
            ^ rotr(words_arr[i - 15], 8)
            ^ words_arr[i - 15] >> 7)
            + (rotr(words_arr[i - 2], 19)
            ^ rotr(words_arr[i - 2], 61)
            ^ words_arr[i - 2] >> 6);
    }

    for (i = 0; i != 80; ++i) {
        t1 = buf_arr[7] + sha512_const[i] + words_arr[i]
        + (buf_arr[4] & buf_arr[5] ^ ~buf_arr[4] & buf_arr[6])
            + (rotr(buf_arr[4], 14) ^ rotr(buf_arr[4], 18) ^ rotr(buf_arr[4], 41));
        t2 = (rotr(buf_arr[0], 28) ^ rotr(buf_arr[0], 34) ^ rotr(buf_arr[0], 39))
            + (buf_arr[0] & buf_arr[1] ^ buf_arr[1] & buf_arr[2] ^ buf_arr[2] & buf_arr[0]);

        buf_arr[7] = buf_arr[6];
        buf_arr[6] = buf_arr[5];
        buf_arr[5] = buf_arr[4];
        buf_arr[4] = buf_arr[3] + t1;
        buf_arr[3] = buf_arr[2];
        buf_arr[2] = buf_arr[1];
        buf_arr[1] = buf_arr[0];
        buf_arr[0] = t1 + t2;
    }

    for (i = 0; i != 8; ++i) {
        val->hash[i] += buf_arr[i];
    }
}